import { momentum } from '../util/momentum'
import { ease } from '../util/ease'
import { style, offset } from '../util/dom'
import {
  PROBE_REALTIME,
  DIRECTION_LEFT,
  DIRECTION_RIGHT,
  DIRECTION_UP,
  DIRECTION_DOWN
} from '../util/const'
import { requestAnimationFrame, cancelAnimationFrame } from '../util/raf'

export function coreMixin (GScroll) {
  GScroll.prototype._start = function (e) {
    if (!this.enabled) {
      return
    }
    e.preventDefault() && e.stopPropagation()

    this.scrolling = true
    this._transitionTime()
    this.stop()

    let point = e.touches ? e.touches[0] : e

    this.directionX = 0
    this.directionY = 0
    this.movingDirectionX = 0
    this.movingDirectionY = 0

    this.startX = this.x
    this.startY = this.y
    this.absStartX = this.x
    this.absStartY = this.y
    this.pointX = point.pageX
    this.pointY = point.pageY

    this.startTime = new Date()
  }

  GScroll.prototype._move = function (e) {
    if (!this.enabled && !this.scrolling) {
      return
    }
    e.preventDefault() && e.stopPropagation()

    let point = e.touches ? e.touches[0] : e

    let deltaX = point.pageX - this.pointX
    let deltaY = point.pageY - this.pointY
    this.pointX = point.pageX
    this.pointY = point.pageY

    let newX = this.x + deltaX
    let newY = this.y + deltaY
    this.movingDirectionX =
      deltaX > 0 ? DIRECTION_RIGHT : deltaX < 0 ? DIRECTION_LEFT : 0
    this.movingDirectionY =
      deltaY > 0 ? DIRECTION_DOWN : deltaY < 0 ? DIRECTION_UP : 0

    //  超出边界，滑动距离减少
    if (newX > 0 || newX < this.maxScrollX) {
      newX = this.x + deltaX / 3
    }
    if (newY > 0 || newY < this.maxScrollY) {
      newY = this.y + deltaY / 3
    }

    newX = this.hasHorizontalScroll ? newX : 0
    newY = this.hasVerticalScroll ? newY : 0

    this._translate(newX, newY)

    if (this.options.probeType === PROBE_REALTIME) {
      this._startProbe()
    }

    this.trigger('scroll', {
      x: newX,
      y: newY
    })

    let scrollLeft =
      document.documentElement.scrollLeft ||
      window.pageXOffset ||
      document.body.scrollLeft
    let scrollTop =
      document.documentElement.scrollTop ||
      window.pageYOffset ||
      document.body.scrollTop

    let pX = this.pointX - scrollLeft
    let pY = this.pointY - scrollTop

    if (
      pX >
        document.documentElement.clientWidth -
          this.options.momentumLimitDistance ||
      pX < this.options.momentumLimitDistance ||
      pY >
        document.documentElement.clientHeight -
          this.options.momentumLimitDistance ||
      pY < this.options.momentumLimitDistance
    ) {
      this._end(e)
    }
  }

  GScroll.prototype._end = function (e) {
    if (!this.enabled && !this.scrolling) {
      return
    }
    e.preventDefault() && e.stopPropagation()

    this.scrolling = false
    this.isInTransition = false

    if (this.options.pullDownRefresh && this._checkPullDown()) {
      return
    }

    if (!this.options.snap && this.resetPosition()) {
      return
    }

    let newX = Math.round(this.x)
    let newY = Math.round(this.y)

    let deltaX = newX - this.absStartX
    let deltaY = newY - this.absStartY
    this.directionX =
      deltaX > 0 ? DIRECTION_RIGHT : deltaX < 0 ? DIRECTION_LEFT : 0
    this.directionY =
      deltaY > 0 ? DIRECTION_DOWN : deltaY < 0 ? DIRECTION_UP : 0

    this.endTime = new Date()

    let easing = ease.swipe
    let duration = this.endTime - this.startTime
    let absDistX = Math.abs(newX - this.startX)
    let absDistY = Math.abs(newY - this.startY)

    if (
      !this.options.snap &&
      duration < this.options.momentumLimitTime &&
      (absDistX > this.options.momentumLimitDistance ||
        absDistY > this.options.momentumLimitDistance)
    ) {
      let momentumX = this.hasHorizontalScroll
        ? momentum(
          this.x,
          this.startX,
          duration,
          this.maxScrollX,
          this.minScrollX,
          this.wrapperWidth,
          this.options
        )
        : {
          destination: newX,
          duration: 0
        }
      let momentumY = this.hasVerticalScroll
        ? momentum(
          this.y,
          this.startY,
          duration,
          this.maxScrollY,
          this.minScrollY,
          this.wrapperHeight,
          this.options
        )
        : {
          destination: newY,
          duration: 0
        }

      let newTime = Math.max(momentumX.duration, momentumY.duration)
      newX = this.hasHorizontalScroll ? momentumX.destination : 0
      newY = this.hasVerticalScroll ? momentumY.destination : 0

      // 超出边界，滑动距离减少
      const bounceDist = 40
      newX =
        newX > this.minScrollX
          ? this.minScrollX + bounceDist
          : newX < this.maxScrollX
            ? this.maxScrollX - bounceDist
            : newX
      newY =
        newY > this.minScrollY
          ? this.minScrollY + bounceDist
          : newY < this.maxScrollY
            ? this.maxScrollY - bounceDist
            : newY

      this.scrollTo(newX, newY, newTime, easing)
    }

    if (this.options.snap) {
      let time = 300
      let snap = this._nearestSnap(newX, newY)
      newX = snap.x
      newY = snap.y
      easing = ease.swipeBounce
      this.scrollTo(newX, newY, time, easing)
    }

    this.trigger('scrollEnd', {
      x: newX,
      y: newY
    })
  }

  GScroll.prototype._transitionTime = function (time = 0) {
    this.scrollerStyle[style.transitionDuration] = time + 'ms'
  }

  GScroll.prototype._transitionTimingFunction = function (easing) {
    this.scrollerStyle[style.transitionTimingFunction] = easing
  }

  GScroll.prototype._translate = function (x, y) {
    x = this.hasHorizontalScroll ? x : 0
    y = this.hasVerticalScroll ? y : 0

    this.scrollerStyle[style.transform] = `translate(${x}px, ${y}px) translateZ(0)`

    this.x = x
    this.y = y

    this.isInTransition = true
  }

  GScroll.prototype._transitionEnd = function (e) {
    if (this.isInTransition) {
      this.resetPosition()
      this.isInTransition = false
    }

    this.trigger('transitionEnd', {
      x: this.x,
      y: this.y
    })

    this.trigger('scrollEnd', {
      x: this.x,
      y: this.y
    })
  }

  GScroll.prototype.resetPosition = function () {
    let needReSet = false
    let easing = ease.bounce
    let newX = Math.round(this.x)
    let newY = Math.round(this.y)

    if (newX > this.minScrollX || newY > this.minScrollY) {
      newX = this.minScrollX
      newY = this.minScrollY
      needReSet = true
    }
    if (newX < this.maxScrollX || newY < this.maxScrollY) {
      newX = this.maxScrollX
      newY = this.maxScrollY
      needReSet = true
    }

    // 下拉刷新，返回
    if (!this.pullingDown && needReSet) {
      newX = this.hasHorizontalScroll ? newX : 0
      newY = this.hasVerticalScroll ? newY : 0
      this.scrollTo(newX, newY, this.options.bounceTime, easing)

      this.trigger('scrollEnd', {
        x: newX,
        y: newY
      })
    }

    this.refresh()

    return needReSet
  }

  GScroll.prototype.scrollTo = function (x, y, time, easing = ease.bounce) {
    this.isInTransition =
      this.options.useTransition && time > 0 && (x !== this.x || y !== this.y)

    if (!time || this.options.useTransition) {
      this._transitionTimingFunction(easing.style)
      this._transitionTime(time)
      this._translate(x, y)

      if (time && this.options.probeTypeType === PROBE_REALTIME) {
        this._startProbe()
      }
    }
  }

  GScroll.prototype.scrollToElement = function (
    el,
    time,
    offsetX,
    offsetY,
    easing
  ) {
    if (!el) {
      return
    }

    el = el.nodeType ? el : this.scroller.querySelector(el)

    let pos = offset(el)
    pos.left -= this.wrapperOffset.left
    pos.top -= this.wrapperOffset.top
    pos.left =
      pos.left > this.minScrollX
        ? this.minScrollX
        : pos.left < this.maxScrollX
          ? this.maxScrollX
          : pos.left
    pos.top =
      pos.top > this.minScrollY
        ? this.minScrollY
        : pos.top < this.maxScrollY
          ? this.maxScrollY
          : pos.top

    this.scrollTo(pos.left, pos.top, time, easing)
  }

  GScroll.prototype._startProbe = function () {
    cancelAnimationFrame(this.probeTimer)
    this.probeTimer = requestAnimationFrame(probe)

    let me = this

    function probe () {
      let pos = me.getComputedPosition()
      me.trigger('scroll', pos)
      if (!me.isInTransition) {
        me.trigger('scrollEnd', pos)
        return
      }
      me.probeTimer = requestAnimationFrame(probe)
    }
  }

  GScroll.prototype.getComputedPosition = function () {
    let transform = getComputedStyle(this.scroller)['transform']
    let matrix = transform.replace(/[^\d-.,]/g, '').split(',')

    let newX = Number(matrix[4])
    let newY = Number(matrix[5])

    return {
      x: newX,
      y: newY
    }
  }

  GScroll.prototype.stop = function () {
    let pos = this.getComputedPosition()

    if (!this.pullingDown && this.resetPosition()) {
      return
    }

    if (this.isInTransition) {
      this.isInTransition = false
    }

    cancelAnimationFrame(this.probeTimer)

    if (pos.x || pos.y) {
      let newX = this.hasHorizontalScroll ? pos.x : 0
      let newY = this.hasVerticalScroll ? pos.y : 0
      this._translate(newX, newY)

      this.trigger('scrollEnd', {
        x: newX,
        y: newY
      })
    }
  }
}
